#include "sprite2d.h"
#include "platform/vk/vk_memory.h"

namespace vkrender
{
    std::vector<VkVertexInputBindingDescription> SpriteVertex::getBindingDescriptions()
    {
        std::vector<VkVertexInputBindingDescription> bindingDescriptions(1);
        bindingDescriptions[0].binding = 0;
        bindingDescriptions[0].stride = sizeof(SpriteVertex);
        bindingDescriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

        return bindingDescriptions;
    }

    std::vector<VkVertexInputAttributeDescription> SpriteVertex::getAttributeDescriptions()
    {
        std::vector<VkVertexInputAttributeDescription> attributeDescriptions(3);

        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[0].offset = 0;

        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(SpriteVertex, color);

        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[2].offset = offsetof(SpriteVertex, texcoords);

        return attributeDescriptions;
    }

    SpriteBuffer::SpriteBuffer(const std::shared_ptr<VulkanDeviceContext>& context,
        uint32_t max_index_count)
    {
        this->context = context;
        this->device = context->device;
        this->physicalDevice = context->physicalDevice;
        this->max_index_count = max_index_count;
        this->vertex_count = 0;
        this->index_count = 0;
        this->vertex_mapping = nullptr;
        this->index_mapping = nullptr;

        create();
    }

    SpriteBuffer::~SpriteBuffer()
    {
        vkDestroyBuffer(device, indexbuffer, nullptr);
        vkFreeMemory(device, indexmemory, nullptr);
        vkDestroyBuffer(device, vertexbuffer, nullptr);
        vkFreeMemory(device, vertexmemory, nullptr);
    }

    void SpriteBuffer::bind(VkCommandBuffer commandBuffer)
    {
        VkBuffer buffers[] = { vertexbuffer };
        VkDeviceSize offsets[] = { 0 };
        vkCmdBindVertexBuffers(commandBuffer, 0, 1, buffers, offsets);
        vkCmdBindIndexBuffer(commandBuffer, indexbuffer, 0, VkIndexType::VK_INDEX_TYPE_UINT32);
    }

    void SpriteBuffer::draw(VkCommandBuffer commandBuffer)
    {
        // vkCmdDraw(commandBuffer, vertex_count, 1, 0, 0);
        vkCmdDrawIndexed(commandBuffer, index_count, 1, 0, 0, 0);
    }

    bool SpriteBuffer::append(const SpriteQuad& sprite)
    {
        if (sprite.num_vertices() + vertex_count > max_index_count)
            return false;

        // todo: 
        SpriteVertex* ptr = vertex_mapping + vertex_count;
        ptr[0].pos = sprite.pos;
        ptr[0].color = sprite.color;
        ptr[0].texcoords = { 0.0f, 0.0f };
        ptr[1].pos = { sprite.pos.x, sprite.pos.y + sprite.size.y };
        ptr[1].color = sprite.color;
        ptr[1].texcoords = { 0.0f, 1.0f };
        ptr[2].pos = { sprite.pos.x + sprite.size.x, sprite.pos.y + sprite.size.y };
        ptr[2].color = sprite.color;
        ptr[2].texcoords = { 1.0f, 1.0f };
        ptr[3].pos = { sprite.pos.x + sprite.size.x, sprite.pos.y };
        ptr[3].color = sprite.color;
        ptr[3].texcoords = { 1.0f, 0.0f };

        uint32_t* index_ptr = index_mapping + index_count;
        index_ptr[0] = vertex_count + 0;
        index_ptr[1] = vertex_count + 1;
        index_ptr[2] = vertex_count + 2;
        index_ptr[3] = vertex_count + 0;
        index_ptr[4] = vertex_count + 2;
        index_ptr[5] = vertex_count + 3;

        vertex_count += sprite.num_vertices();
        index_count += sprite.num_indices();
        return true;
    }

    void SpriteBuffer::begin()
    {
        VkDeviceSize vertexbufferSize = max_index_count * sizeof(SpriteVertex);
        vkMapMemory(device, vertexmemory, 0, vertexbufferSize, 0, (void**)&vertex_mapping);
        VkDeviceSize indexbuffersize = max_index_count * sizeof(uint32_t);
        vkMapMemory(device, indexmemory, 0, indexbuffersize, 0, (void**)&index_mapping);
        index_count = 0;
        vertex_count = 0;
    }

    void SpriteBuffer::flush()
    {
        vkUnmapMemory(device, vertexmemory);
        vkUnmapMemory(device, indexmemory);
        vertex_mapping = nullptr;
        index_mapping = nullptr;
    }

    void SpriteBuffer::create()
    {
        // TODO: when performance is not critical, no staging buffer is required.
        VkDeviceSize vertexbuffersize = max_index_count * sizeof(SpriteVertex);
        VkDeviceSize indexbuffersize = max_index_count * sizeof(uint32_t);
        this->context->createBuffer(
            // device, physicalDevice,
            vertexbuffersize,
            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            vertexbuffer,
            vertexmemory);
        this->context->createBuffer(
            // device, physicalDevice, 
            indexbuffersize,
            VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            indexbuffer,
            indexmemory
        );
    }
}
